/*
 * $Id: JDateTime.java 30 2006-06-08 13:26:40Z wjx $
 */
package com.someok.utils.datetime;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.HashMap;
import java.util.TimeZone;

import com.someok.utils.datetime.converters.CalendarConverter;
import com.someok.utils.datetime.converters.DateConverter;
import com.someok.utils.datetime.converters.GregorianCalendarConverter;
import com.someok.utils.datetime.converters.JDateTimeConverter;
import com.someok.utils.datetime.converters.SqlDateConverter;
import com.someok.utils.datetime.converters.SqlTimestampConverter;
import com.someok.utils.datetime.formatters.DefaultFormatter;
import com.someok.utils.datetime.names.EnglishNames;

/**
 * Universal all-in-one date and time class that uses Astronomical Julian
 * Dates for time calculations. Guarantied precision is up to 0.001 sec. <p>
 *
 * <code>JDateTime</code> contains date/time information for current day. By
 * default, behaviour and formats are set to ISO standard, although this may
 * be changed.<p>
 *
 * <code>JDateTime</code> can be set in many different ways by using setXxx()
 * methods or equivalent constructors. Moreover, it may be set from an instance
 * of any available java date-time class. This functionality can be easily
 * enhanced for any custom date/time class. Furthermore, <code>JDateTime</code>
 * can be used for generation of any such date/time class.<p>
 *
 * Rolling dates with <code>JDateTime</code> is easy. For this
 * <code>JDateTime</code> contains many addXxx() methods. Time can be added
 * or subtracted with any value or more values at once. All combinations are
 * valid. Calculations also performs month fixes by default.<p>
 *
 * <code>JDateTime</code> behaviour is set by several attributes (or
 * parameters). Each one contains 2 values: one is the default value, used by
 * all instances of <code>JDateTime</code> and the other one is just for a
 * specific instance of <code>JDateTime</code>. This means that it is
 * possible to set behavior of all instances at once or of one particular
 * instance.<p>
 *
 * Bellow is the list of behavior attributes:
 *
 * <ul>
 *
 * <li>monthFix - since months do not have the same number of days, adding
 * months and years may be calculated in two different ways: with or
 * without month fix. when month fix is on, <code>JDateTime</code> will
 * additionally fix all time adding and fix the date. For example, adding
 * one month to 2003-01-31 will give 2003-02-28 and not 2003-03-03.
 * By default, monthFix is turned on and set to <code>true</code>.
 * </li>
 *
 * <li>names - are objects that holds various date's names and text for a
 * specific languages. Names are used during formating the output string.
 * </li>
 *
 * <li>format template - is String that describes how time is converted to
 * and from a String. Default format matches ISO standard. An instance of
 * <code>JdtFormatter</code> parses and uses this template.</li>
 *
 * <li>week definition - is used for specifying the definition of the week.
 * Week is defined with first day of the week and with the must have day. A
 * must have day is a day that must exist in the 1st week of the year. For
 * example, default value is Thursday (4) as specified by ISO standard.
 * Alternatively, instead of must have day, minimal days of week may be used,
 * since this two numbers are in relation.
 * </li>
 *
 * </ul>
 *
 * Optimization: although based on heavy calculations, <code>JDateTime</code>
 * works significantly faster then java's <code>Calendar</code>s. Since
 * <code>JDateTime</code> doesn't use lazy initialization, setXxx() method is
 * slower. However, this doesn't have much effect to the global performances:
 * settings are not used without gettings. As soon as any other method is
 * used (getXxx() or addXxx()) performances of <code>JDateTime</code> becomes
 * significantly better.
 *
 * <p><a href="JDateTime.java.html"><i>View Source</i></a></p>
 *
 * @version $Revision: 30 $ $Date: 2006-06-08 21:26:40 +0800 (星期四, 08 六月 2006) $
 */
public class JDateTime implements Comparable {

	/**
	 * DateTime stamp for current date.
	 *
	 * @see DateTimeStamp
	 */
	private DateTimeStamp time = new DateTimeStamp();

	/**
	 * Day of week, range: [1-7] == [Monday - Sunday]
	 */
	private int dayofweek;

	/**
	 * Day of year, range: [1-365] or [1-366]
	 */
	private int dayofyear;

	/**
	 * Leap year flag.
	 */
	private boolean leap;

	/**
	 * Week of year, range: [1-52] or [1-53]
	 */
	private int weekofyear;


	private int weekofmonth;

	/**
	 * Julian Date for 1970-01-01.
	 */
	public static final JulianDateStamp JD_1970 = new JulianDateStamp(2440587.5);

	/**
	 * Current Julian Date.
	 */
	private JulianDateStamp jdate;

	// ---------------------------------------------------------------- julian date (CORE)

	/**
	 * Sets current Julian Date. This is the core of the JDateTime class and it
	 * is used by all other classes. This method performs all calculations
	 * required for whole class.
	 *
	 * @param jds    current julian date
	 */
	public void setJulianDate(JulianDateStamp jds) {
		setJdOnly(jds);
		setParams();
	}

	/**
	 * Internal method for setting various parameters other then date/time.
	 */
	private void setParams() {
		this.leap = TimeUtil.isLeapYear(time.year);
		this.dayofweek = calcDayOfWeek();
		this.dayofyear = calcDayOfYear();
		MONTH_LENGTH[2] = this.leap ? 29 : 28;
		this.weekofyear = calcWeekOfYear(getFirstDayOfWeek() ,getMustHaveDayOfFirstWeek());
		this.weekofmonth = weekNumber(time.day, this.dayofweek);
	}

	/**
	 * Internal method that just sets the time stamp and not all other additional
	 * parameters. Used for faster calculations only and only by main core
	 * set/add methods.
	 *
	 * @param jds    julian date
	 */
	private void setJdOnly(JulianDateStamp jds) {
		jdate = jds;
		time = TimeUtil.fromJulianDate(jds);
	}

	/**
	 * Core method that sets date and time. All others set() methods use this
	 * one. Milliseconds are truncated after 3rd digit.
	 *
	 * @param year   year to set
	 * @param month  month to set
	 * @param day    day to set
	 * @param hour   hour to set
	 * @param minute minute to set
	 * @param second second to set
	 */
	public void set(int year, int month, int day, int hour, int minute, double second) {

		// fix seconds fractions because of float point arithmetics
        //second = ((int) second) + ((int) ((second - (int)second) * 1000 + 1e-9) / 1000.0);
		double ms = (second - (int)second) * 1000;
		if (ms > 999) {
			ms = 999;
		} else {
			ms += 1e-9;
		}
		second = ((int) second) + ((int) ms / 1000.0);
		jdate = TimeUtil.toJulianDate(year, month, day, hour, minute, second);

		// if given time is valid it means that there is no need to calculate it
		// again from already calculated julian date. however, it is still
		// necessary to fix milliseconds to match the value that would be
		// calculated as setJulianDate() is used. This fix only deals with the
		// time, not doing the complete and more extensive date calculation.
		// this means that method works faster when regular date is specified.
		if (TimeUtil.isValidDateTime(year, month, day, hour, minute, second)) {

			int ka = (int)(jdate.fraction + 0.5);
			double frac = jdate.fraction + 0.5 - ka + 1e-10;

			// hour with minute and second included as fraction
			double d_hour = frac * 24.0;

			// minute with second included as a fraction
			double d_minute = (d_hour - (int)d_hour) * 60.0;

			second = (d_minute - (int)d_minute) * 60.0;

			// fix calculation errors
			second = ((int) (second * 10000) + 0.5) / 10000.0;

			time.year = year; time.month = month; time.day = day;
			time.hour = hour; time.minute = minute; time.second = second;
			setParams();
		} else {
			setJulianDate(jdate);
		}
	}

	/**
	 * Sets just Julian Date and no other parameter such as day of week etc. It
	 * is used internally for speed.
	 *
	 * @param year   year to set
	 * @param month  month to set
	 * @param day    day to set
	 * @param hour   hour to set
	 * @param minute minute to set
	 * @param second second to set
	 */
	private void setJdOnly(int year, int month, int day, int hour, int minute, double second) {
		setJdOnly(TimeUtil.toJulianDate(year, month, day, hour, minute, second));
	}

	/**
	 * Returns Julian Date stamp.
	 *
	 * @return Julian Date stamp
	 */
	public JulianDateStamp getJulianDate() {
		return jdate;
	}


	// ---------------------------------------------------------------- core calculations

	/**
	 * Calculates day of week.
	 */
	private int calcDayOfWeek() {
		int jd = (int)(jdate.doubleValue() + 0.5);
		return (jd % 7) + 1;
		//return (jd + 1) % 7;		// return 0 (Sunday), 1 (Monday),...
	}

	private static final int NUM_DAYS[] = {-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};		// 1-based
    private static final int LEAP_NUM_DAYS[] = {-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};	// 1-based

	/**
	 * Calculates day of year.
	 */
	private int calcDayOfYear() {
		if (leap == true) {
			return LEAP_NUM_DAYS[time.month] + time.day;
		}
		return NUM_DAYS[time.month] + time.day;
	}


	/**
	 * Calculates week of year. Based on:
	 * "Algorithm for Converting Gregorian Dates to ISO 8601 Week Date"
	 * by Rick McCarty, 1999
	 *
	 * @param start  first day of week
	 * @param must   must have day of week
	 *
	 * @return week of year number
	 */
	private int calcWeekOfYear(int start, int must) {

		// is modification required?
		// modification is a fix for the days of year because of the different
		// starting day of week. when modification is required, one week is added
		// or subtracted to the current day, so calculation of the week of year
		// would be correct.
		int delta = 0;
		if (start <= this.dayofweek) {
			if (must < start) {
				delta = 7;
			}
		} else {
			if (must >= start) {
				delta = -7;
			}
		}

		int jd = (int)(jdate.doubleValue() + 0.5) + delta;
		int WeekDay = (jd % 7) + 1;

		int time_year = time.year;
		int DayOfYearNumber = this.dayofyear + delta;
		if (DayOfYearNumber < 1) {
			time_year--;
			DayOfYearNumber = TimeUtil.isLeapYear(time_year) ? 366 + DayOfYearNumber: 365 + DayOfYearNumber;
		} else if (DayOfYearNumber > (this.leap ? 366 : 365)) {
			DayOfYearNumber = this.leap ? DayOfYearNumber - 366: DayOfYearNumber - 365;
			time_year++;
		}

		// modification, if required, is finished. proceed to the calculation.

		int firstDay = jd - DayOfYearNumber + 1;
		int Jan1WeekDay = (firstDay % 7) + 1;

		// find if the date falls in YearNumber Y - 1 set WeekNumber to 52 or 53
		int YearNumber = time_year;
		int WeekNumber = 52;
		if ((DayOfYearNumber <= (8 - Jan1WeekDay)) && (Jan1WeekDay > must)) {
			YearNumber--;
			if ((Jan1WeekDay == must + 1) || ( (Jan1WeekDay == must + 2) && (TimeUtil.isLeapYear(YearNumber)) ) ) {
				WeekNumber = 53;
			}
		}

		// set WeekNumber to 1 to 53 if date falls in YearNumber
		int m = 365, n;
		if (YearNumber == time_year) {
			if (TimeUtil.isLeapYear(time_year) == true) {
				m = 366;
			}
			if ((m - DayOfYearNumber) < (must - WeekDay)) {
				YearNumber = time_year + 1;
				WeekNumber = 1;
			}
		}

		if (YearNumber == time_year) {
			n = DayOfYearNumber + (7 - WeekDay) + (Jan1WeekDay - 1);
			WeekNumber = n / 7;
			if (Jan1WeekDay > must) {
				WeekNumber -= 1;
			}
		}
		return WeekNumber;
	}

	/**
	 * Return the week number of a day, within a period. This may be the week number in
	 * a year, or the week number in a month. Usually this will be a value >= 1, but if
	 * some initial days of the period are excluded from week 1, because
	 * minimalDaysInFirstWeek is > 1, then the week number will be zero for those
	 * initial days. Requires the day of week for the given date in order to determine
	 * the day of week of the first day of the period.
	 *
	 * @param dayOfPeriod
	 *                  Day-of-year or day-of-month. Should be 1 for first day of period.
	 * @param dayOfWeek
	 *
	 * @return Week number, one-based, or zero if the day falls in part of the
	 *         month before the first week, when there are days before the first
	 *         week because the minimum days in the first week is more than one.
	 */
	private final int weekNumber(int dayOfPeriod, int dayOfWeek) {
		// Determine the day of the week of the first day of the period
		// in question (either a year or a month).  Zero represents the
		// first day of the week on this calendar.
		int periodStartDayOfWeek = (dayOfWeek - getFirstDayOfWeek() - dayOfPeriod + 1) % 7;
		if (periodStartDayOfWeek < 0) {
			periodStartDayOfWeek += 7;
		}

		// Compute the week number.  Initially, ignore the first week, which
		// may be fractional (or may not be).  We add periodStartDayOfWeek in
		// order to fill out the first week, if it is fractional.
		int weekNo = (dayOfPeriod + periodStartDayOfWeek - 1) / 7;

		// If the first week is long enough, then count it.  If
		// the minimal days in the first week is one, or if the period start
		// is zero, we always increment weekNo.
		if ((7 - periodStartDayOfWeek) >= getMinimalDaysInFirstWeek()) {
			++weekNo;
		}

		return weekNo;
	}

	// ----------------------------------------------------------------	compare & clone

	/**
	 * Compares current JDateTime object with another one, up to 1 millisecond.
	 *
	 * @param gt     JDateTime to compare
	 *
	 * @return -1 if the current object is less than the argument, 0 if the argument is
	 *         equal, and 1 if the current object is greater than the argument
	 */
	public int compareTo(Object gt) {
		return time.compareTo(((JDateTime)gt).getDateTimeStamp());
	}

	// ---------------------------------------------------------------- add time

	/**
	 * Adds time to current time. The main add method. All other add() methods
	 * must use this one. <p>
	 *
	 * There are 2 different kind of addings, when months are added:
	 * <ul>
	 * <li>when months are not specially concerne, and month is aproximated as 31
	 * days. example: 2003-01-31 + 0-01-0 = 2003-03-03</li>
	 * <li>when months addition is fixed, and month is not approximated.
	 * example: 2003-01-31 + 0-01-0 = 2003-28-02</li>
	 * </ul>
	 * <p>
	 *
	 * @param year     delta year
	 * @param month    delta month
	 * @param day      delta days
	 * @param hour     delta hours
	 * @param minute   delta minutes
	 * @param second   delta seconds
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */

	public void add(int year, int month, int day, int hour, int minute, double second, boolean monthFix) {
		second += time.second;
		minute += time.minute;
		hour += time.hour;
		day += time.day;
		if (monthFix == false) {
			month += time.month;
			year += time.year;
			set(year, month, day, hour, minute, second);
		} else {
			// month fix:
			// 1. add all except month and year
			// 2. store day value
			// 3. add just months
			// 4. if new date is not equal to stored, return to last day of previous month
			setJdOnly(time.year, time.month, day, hour, minute, second);
			int from = time.day;
			month += time.month + (year * 12);		// delta years to add are converted to delta months
			setJdOnly(time.year, month, time.day, time.hour, time.minute, time.second);
			if (time.day < from) {
				set(time.year, time.month, 0, time.hour, time.minute, time.second);
			} else {
				setParams();
			}

			/*// 5. store month value
			// 6. add just year
			// 7. if new month is not equal to stored, rturn to last day of previous month
			from = time.month;
			year += time.year;
			setJdOnly(year, time.month, time.day, time.hour, time.minute, time.second);
			if (time.month > from) {
				set(time.year, time.month, 0, time.hour, time.minute, time.second);
			}*/
		}
	}


	/**
	 * Performs time adding with preset value of monthFix attribute.
	 *
	 * @param year   delta year
	 * @param month  delta month
	 * @param day    delta days
	 * @param hour   delta hours
	 * @param minute delta minutes
	 * @param second delta seconds
	 *
	 * @see #add(int, int, int, int, int, double, boolean)
	 */
	public void add(int year, int month, int day, int hour, int minute, double second) {
		add(year, month, day, hour, minute, second, getMonthFix());
	}


	/**
	 * Adds date, leaving time unchanged.
	 *
	 * @param year     years to add
	 * @param month    months to add
	 * @param day      days to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 *
	 * @see #add(int, int, int, int, int, double, boolean)
	 */
	public void add(int year, int month, int day, boolean monthFix) {
		add(year, month, day, 0, 0, 0, monthFix);
	}
	/**
	 * Adds date, leaving time unchanged, with preset value of monthFix.
	 * attribute.
	 *
	 * @param year   years to add
	 * @param month  months to add
	 * @param day    days to add
	 *
	 * @see #add(int, int, int, boolean)
	 */
	public void add(int year, int month, int day) {
		add(year, month, day, getMonthFix());
	}


	/**
	 * Adds time.
	 *
	 * @param hour     hours to add
	 * @param minute   minutes to add
	 * @param second   seconds to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 *
	 * @see #add(int, int, int, int, int, double)
	 */
	public void addTime(int hour, int minute, double second, boolean monthFix) {
		add(0, 0, 0, hour, minute, second, monthFix);
	}
	/**
	 * Adds time, with preset value of monthFix.
	 *
	 * @param hour   hours to add
	 * @param minute minutes to add
	 * @param second seconds to add
	 *
	 * @see #addTime(int, int, double, boolean)
	 */
	public void addTime(int hour, int minute, double second) {
		addTime(hour, minute, second, getMonthFix());
	}


	/**
	 * Adds year.
	 *
	 * @param y        year to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */
	public void addYear(int y, boolean monthFix) {
		add(y, 0, 0, monthFix);
	}
	/**
	 * Adds year, with preset value of monthFix.
	 *
	 * @param y        year to add
	 */
	public void addYear(int y) {
		addYear(y, getMonthFix());
	}


	/**
	 * Adds month.
	 *
	 * @param m        month to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */
	public void addMonth(int m, boolean monthFix) {
		add(0, m, 0, monthFix);
	}
	/**
	 * Adds month, with preset value of monthFix.
	 *
	 * @param m        month to add
	 */
	public void addMonth(int m) {
		addMonth(m, getMonthFix());
	}

	/**
	 * Adds days.
	 *
	 * @param d      days to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */
	public void addDay(int d, boolean monthFix) {
		add(0, 0, d, monthFix);
	}
	/**
	 * Adds days, with preset value of monthFix.
	 *
	 * @param d      days to add
	 */
	public void addDay(int d) {
		addDay(d, getMonthFix());
	}

	/**
	 * Adds hours.
	 *
	 * @param h      hours to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */
	public void addHour(int h, boolean monthFix) {
		addTime(h, 0, 0, monthFix);
	}
	/**
	 * Adds hours, with preset value of monthFix.
	 *
	 * @param h      hours to add
	 */
	public void addHour(int h) {
		addHour(h, getMonthFix());
	}


	/**
	 * Adds minutes.
	 *
	 * @param m      minutes to add.
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */
	public void addMinute(int m, boolean monthFix) {
		addTime(0, m, 0, monthFix);
	}
	/**
	 * Adds minutes, with preset value of monthFix.
	 *
	 * @param m      minutes to add.
	 */
	public void addMinute(int m) {
		addMinute(m, getMonthFix());
	}

	/**
	 * Adds seconds.
	 *
	 * @param s      seconds to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */
	public void addSecond(double s, boolean monthFix) {
		addTime(0, 0, s, monthFix);
	}
	/**
	 * Adds seconds, with preset value of monthFix.
	 *
	 * @param s      seconds to add
	 */
	public void addSecond(double s) {
		addSecond(s, getMonthFix());
	}


	/**
	 * Adds milliseconds.
	 *
	 * @param ms     miliseconds to add
	 * @param monthFix <code>true</code> for month fixing, <code>false</code> otherwise
	 */
	public void addMillisecond(int ms, boolean monthFix) {
		addTime(0, 0, ms / 1000.0, monthFix);
	}
	/**
	 * Adds milliseconds, with preset value of monthFix.
	 *
	 * @param ms     miliseconds to add
	 */
	public void addMillisecond(int ms) {
		addMillisecond(ms, getMonthFix());
	}

	// ----------------------------------------------------------------	constructors/sets/gets

	/**
	 * Constructor that set date and time.
	 *
	 * @param year   year to set
	 * @param month  month to set
	 * @param day    day to set
	 * @param hour   hours to set
	 * @param minute minutes to set
	 * @param second seconds to set
	 *
	 * @see #set(int, int, int, int, int, double)
	 */
	public JDateTime(int year, int month, int day, int hour, int minute, double second) {
		this.set(year, month, day, hour, minute, second);
	}

	/**
	 * Sets date, time is set to midnight (00:00:00.000).
	 *
	 * @param year   year to set
	 * @param month  month to set
	 * @param day    day to set
	 */
	public void set(int year, int month, int day) {
		set(year, month, day, 0, 0, 0);
	}

	/**
	 * Constructor that sets just date. Time is set to 00:00:00.
	 *
	 * @param year   year to set
	 * @param month  month to set
	 * @param day    day to set
	 *
	 * @see #set(int, int, int)
	 */
	public JDateTime(int year, int month, int day) {
		this.set(year, month, day);
	}

	/**
	 * Sets time, date is unchanged.
	 *
	 * @param hour   hours to set
	 * @param minute minutes to set
	 * @param second secnds to set
	 */
	public void setTime(int hour, int minute, double second) {
		set(time.year, time.month, time.day, hour, minute, second);
	}

	/**
	 * Sets date, time remains unchanged.
	 *
	 * @param year   year
	 * @param month  month
	 * @param day    day
	 */
	public void setDate(int year, int month, int day) {
		set(year, month, day, time.hour, time.minute, time.second);
	}


	// ---------------------------------------------------------------- set from milliseconds

	/**
	 * Constructor that sets current time specified as time in milliseconds, from
	 * the midnight, January 1, 1970 UTC.
	 *
	 * @param milis  time in milliseconds, from the midnight, January 1, 1970 UTC
	 *
	 * @see #set(long )
	 */
	public JDateTime(long milis) {
		set(milis);
	}

	private static final double MILIS_IN_DAY = 1000 * 60 * 60 * 24;

	/**
	 * Sets the time based on current time in milliseconds. Current time is
	 * calculated from the midnight, January 1, 1970 UTC.
	 *
	 * @param milis  time in milliseconds, from the midnight, January 1, 1970 UTC
	 */
	public void set(long milis) {
		BigDecimal bd =	JD_1970.toBigDecimal();
		milis += TimeZone.getDefault().getOffset(milis);
		BigDecimal delta = new BigDecimal(milis / MILIS_IN_DAY);
		JulianDateStamp now = new JulianDateStamp(bd.add(delta));
		setJulianDate(now);
	}


	// ---------------------------------------------------------------- date/time sets

	/**
	 * Sets current year.
	 *
	 * @param y      year to set
	 */
	public void setYear(int y) {
		setDate(y, time.month, time.day);
	}

	/**
	 * Sets current month.
	 *
	 * @param m      month to set
	 */
	public void setMonth(int m) {
		setDate(time.year, m, time.day);
	}

	/**
	 * Sets current day.
	 *
	 * @param d      day to set
	 */
	public void setDay(int d) {
		setDate(time.year, time.month, d);
	}

	/**
	 * Set current hour.
	 *
	 * @param h      hour to set
	 */
	public void setHour(int h) {
		setTime(h, time.minute, time.second);
	}

	/**
	 * Set current minute.
	 *
	 * @param m      minutes to set
	 */
	public void setMinute(int m) {
		setTime(time.hour, m, time.second);

	}

	/**
	 * Sets current second and millisecond.
	 *
	 * @param s      seconds and milliseconds to set
	 */
	public void setSecond(double s) {
		setTime(time.hour, time.minute, s);
	}

	/**
	 * Sets current second.
	 *
	 * @param s      seconds to set
	 */
	public void setSecond(int s) {
		double milis = s + (time.second - (int)time.second);
		setTime(time.hour, time.minute, milis);
	}

	/**
	 * Sets current millisecond.
	 *
	 * @param m      milliseconds to set
	 */
	public void setMillisecond(int m) {
		setTime(time.hour, time.minute, ((int)time.second) + m/1000.0);
	}


	// ----------------------------------------------------------------	date/time gets

	/**
	 * Returns current year.
	 *
	 * @return current year
	 */
	public int getYear() {
		return time.year;
	}

	/**
	 * Returns current month.
	 *
	 * @return current month
	 */
	public int getMonth() {
		return time.month;
	}

	/**
	 * Returns current day of month.
	 *
	 * @return current day of month
	 * @see #getDayOfMonth
	 */
	public int getDay() {
		return time.day;
	}

	/**
	 * Returns current day of month.
	 *
	 * @return current day of month
	 * @see #getDay
	 */
	public int getDayOfMonth() {
		return time.day;
	}

	/**
	 * Returns current hour.
	 *
	 * @return current hour
	 */
	public int getHour() {
		return time.hour;
	}

	/**
	 * Returns current minutes.
	 *
	 * @return current minutes
	 */
	public int getMinute() {
		return time.minute;
	}

	/**
	 * Return current secodns. For an integer value, just cast the returned
	 * value.
	 *
	 * @return current seconds.
	 */
	public double getSecond() {
		return time.second;
	}

	/**
	 * Returns current milliseconds.
	 *
	 * @return current milliseconds
	 */
	public int getMillisecond() {
		return (int) ((time.second - (int)time.second) * 1000 + 1e-9);
	}

	// ----------------------------------------------------------------	other gets

	/**
	 * Returns current day of week.
	 *
	 * @return current day of week
	 */
	public int getDayOfWeek() {
		return dayofweek;
	}

	/**
	 * Returns current day of year.
	 *
	 * @return current day of year
	 */
	public int getDayOfYear() {
		return dayofyear;
	}

	/**
	 * Returns current leap year flag.
	 *
	 * @return current leap year flag
	 */
	public boolean isLeap() {
		return leap;
	}

	/**
	 * Returns current week of year.
	 *
	 * @return current week of year
	 */
	public int getWeekOfYear() {
		return weekofyear;
	}

	public int getWeekOfMonth() {
		return weekofmonth;
	}


	/**
	 * Length of months. Modified by main setJulianDate() method for February and
	 * leap years.
	 */
	private static final int MONTH_LENGTH[] = {0, 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

	/**
	 * Returns the length of the specified month in days.
	 *
	 * @return length of the specified month in days
	 */
	public int getMonthLength(int m) {
		if ((m < 1) || (m > 12)) {
			return -1;
		}
		return MONTH_LENGTH[m];
	}

	/**
	 * Returns the length of the current month in days.
	 *
	 * @return length of the current month in days
	 */
	public int getMonthLength() {
		return MONTH_LENGTH[time.month];
	}

	// ----------------------------------------------------------------	setting the current time

	/**
	 * Sets current local date and time.
	 */
	public void set() {
		loadFrom(Calendar.getInstance());
	}

	/**
	 * Constructor that sets current local date and time.
	 */
	public JDateTime() {
		this.set();
	}

	// ---------------------------------------------------------------- objects and instances

	private static HashMap converters = new HashMap();
	static {
		registerDefaults();
	}

	/**
	 * Registers default converters.
	 */
	public static void registerDefaults() {
		converters.clear();
		register(JDateTime.class, new JDateTimeConverter());
		register(Calendar.class, new CalendarConverter());
		register(java.util.GregorianCalendar.class, new GregorianCalendarConverter());
		register(java.util.Date.class, new DateConverter());
		register(java.sql.Date.class, new SqlDateConverter());
		register(java.sql.Timestamp.class, new SqlTimestampConverter());
	}

	/**
	 * Registers a <code>JdtConvertor</code> for a specific class. Convertor
	 * knows how to read (extract) data from an object, but also how to populate
	 * the same object after it creates a new instance of it. <p>
	 *
	 * Because both reading and creating new instance is required by converter,
	 * it is not possible to register a superclass and work with its subclasses.
	 * Instead, all classes that would be used for the time converters must be
	 * registered separately. Example: java.util.Calendar has a subclass
	 * java.util.GregorianCalendar. But it is not possible to have just converter
	 * for Calendar - there must be converter for GregorianCalendar, too (of course,
	 * if it attended to be used).
	 *
	 * @param c      class of an object that will be instanced and populated with time
	 * @param gtc    converter
	 */
	public static void register(Class c, JdtConverter gtc) {
		converters.put(c, gtc);
	}

	/**
	 * Loads time from an object by using registered converters.
	 *
	 * @param o      object to read time from
	 */
	public void loadFrom(Object o) {
		JdtConverter gtc = (JdtConverter) converters.get(o.getClass());
		if (gtc != null) {
			gtc.load(this, o);
		}
	}

	/**
	 * Stores time to a new instance of desired class, by using converters.
	 *
	 * @param c      class of new object
	 *
	 * @return new instance of specified class, populated with time
	 */
	public Object getInstance(Class c) {
		JdtConverter gtc = (JdtConverter) converters.get(c);
		if (gtc == null) {
			return null;
		}
		return gtc.get(this);
	}

	/**
	 * Shortcut for getInstance() that builds and returns <code>Calendar</code>
	 * instance.
	 *
	 * @return Calendar instance
	 * @see #getInstance
	 */
	public java.util.Calendar getCalendarInstance() {
		return (java.util.Calendar) getInstance(java.util.Calendar.class);
	}

	/**
	 * Shortcut for getInstance() that builds and returns <code>Date</code>
	 * instance.
	 *
	 * @return Date instance
	 * @see #getInstance
	 */
	public java.util.Date getDateInstance() {
		return (java.util.Date) getInstance(java.util.Date.class);
	}

	/**
	 * Shortcut for getInstance() that builds and returns
	 * <code>GregorianCalendar</code> instance.
	 *
	 * @return GregorianCalendar instance
	 * @see #getInstance
	 */
	public java.util.GregorianCalendar getGregorianCalendarInstace() {
		return (java.util.GregorianCalendar) getInstance(java.util.GregorianCalendar.class);
	}

	/**
	 * Shortcut for getInstance() that builds and returns
	 * <code>java.sql.Date</code> instance.
	 *
	 * @return java.sql.Date instance
	 * @see #getInstance
	 */
	public java.sql.Date getSqlDateInstace() {
		return (java.sql.Date) getInstance(java.sql.Date.class);
	}

	/**
	 * Shortcut for getInstance() that builds and returns
	 * <code>java.sql.Timestamp</code> instance.
	 *
	 * @return java.sql.Date instance
	 * @see #getInstance
	 */
	public java.sql.Timestamp getSqlTimestampInstance() {
		return (java.sql.Timestamp) getInstance(java.sql.Timestamp.class);
	}

	/**
	 * Shortcut for getInstance() that builds and returns new
	 * <code>JDateTime</code> instance with the date/time set as the original
	 * one.
	 *
	 * @return new JDateTime instance
	 * @see #getInstance
	 */
	public JDateTime getJDateTimeInstance() {
		return (JDateTime) getInstance(JDateTime.class);
	}


	/**
	 * Stores time to an existing instance of desired class, by using converters.
	 *
	 * @param o      object where to store time info
	 */

	public void storeTo(Object o) {
		JdtConverter gtc = (JdtConverter) converters.get(o.getClass());
		if (gtc != null) {
			gtc.store(this, o);
		}
	}

	/**
	 * Contructs <code>JDateTime</code> from various objects.
	 *
	 * @param o      object from where to read date info
	 */
	public JDateTime(Object o) {
		loadFrom(o);
	}

	/**
	 * Contructs <code>JDateTime</code> from <code>DateTimeStamp</code>.
	 *
	 * @param dts    date time stamp
	 */
	public JDateTime(DateTimeStamp dts) {
		setDateTimeStamp(dts);
	}

	/**
	 * Contructs <code>JDateTime</code> from <code>JulianDateStamp</code>.
	 *
	 * @param jds    date time stamp
	 */
	public JDateTime(JulianDateStamp jds) {
		setJulianDate(jds);
	}


	/**
	 * Sets the new timestamp.
	 *
	 * @param t
	 *
	 * @see #setDateTimeStamp
	 * @deprecated use setDateTimeStamp() instead
	 */
	public void setTimeStamp(DateTimeStamp t) {
		set(t.year, t.month, t.day, t.hour, t.minute, t.second);
	}
	/**
	 * Sets the new timestamp.
	 *
	 * @param t
	 */
	public void setDateTimeStamp(DateTimeStamp t) {
		set(t.year, t.month, t.day, t.hour, t.minute, t.second);
	}

	/**
	 * Returns new object generic time stamp.
	 *
	 * @return timestamp
	 */
	public DateTimeStamp getDateTimeStamp() {
		return new DateTimeStamp(time.year, time.month, time.day, time.hour, time.minute, time.second);
	}

	// ---------------------------------------------------------------- monthFix

	private static boolean defaultMonFix = true;
	private Boolean monFix = null;

	/**
	 * Sets default month fix value.
	 *
	 * @param v      default month fix value
	 */
	public static void setDefaultMonthFix(boolean v) {
		defaultMonFix = v;
	}

	/**
	 * Resets default month fix value to <code>true</code>.
	 */
	public static void resetDefaultMonthFix() {
		defaultMonFix = true;
	}

	/**
	 * Sets month fix value.
	 *
	 * @param v      month fix value
	 */
	public void setMonthFix(boolean v) {
		monFix = new Boolean(v);
	}

	/**
	 * Resets month fix value to default one.
	 */
	public void resetMonthFix() {
		monFix = null;
	}

	/**
	 * Returns actual mont fix value.
	 */
	public boolean getMonthFix() {
		if (monFix != null) {
			return monFix.booleanValue();
		}
		return defaultMonFix;
	}


	// ---------------------------------------------------------------- names

	private static JdtNames defaultNames = new EnglishNames();
	private JdtNames names = null;

	/**
	 * Sets default JDateTime names.
	 *
	 * @param newNames names
	 */
	public static void setDefaultNames(JdtNames newNames) {
		if (newNames != null) {
			defaultNames = newNames;
		}
		return;
	}

	/**
	 * Resets default date time names to English.
	 */
	public static void resetDefaultNames() {
		defaultNames = new EnglishNames();
	}

	/**
	 * Sets current date time names.
	 *
	 * @param newNames
	 */
	public void setNames(JdtNames newNames) {
		if (newNames != null) {
			names = newNames;
		}
		return;
	}

	/**
	 * Reset current names to default values.
	 */
	public void resetNames() {
		names = null;
	}

	/**
	 * Returns actual date time names.
	 *
	 * @return actual date time names.
	 */
	public JdtNames getNames() {
		if (names != null) {
			return names;
		}
		return defaultNames;
	}

	// ---------------------------------------------------------------- formats

	private static final String DEFAULT_FORMAT_TEMPLATE = "YYYY-MM-DD hh:mm:ss.mss";

	private static String defaultFormatTemplate = DEFAULT_FORMAT_TEMPLATE;

	private String formatTemplate = null;

	/**
	 * Sets default format template.
	 *
	 * @param newFormatTemplate
	 */
	public static void setDefaultFormatTemplate(String newFormatTemplate) {
		if (newFormatTemplate != null) {
			defaultFormatTemplate = newFormatTemplate;
		}
		return;
	}

	/**
	 * Resets default format to JDateTime defaults format: YYYY-MM-DD hh:mm:s.m
	 */
	public static void resetDefaultFormatTemplate() {
		defaultFormatTemplate = DEFAULT_FORMAT_TEMPLATE;
		return;
	}

	/**
	 * Sets current format template.
	 *
	 * @param newFormat
	 */
	public void setFormatTemplate(String newFormat) {
		if (newFormat != null) {
			formatTemplate = newFormat;
		}
		return;
	}

	/**
	 * Resets current format template to default value.
	 */
	public void resetFormatTemplate() {
		formatTemplate = null;
	}

	/**
	 * Returns actual format template.
	 *
	 * @return actual format template
	 */
	public String getFormatTemplate() {
		if (formatTemplate != null) {
			return formatTemplate;
		}
		return defaultFormatTemplate;
	}


	// ---------------------------------------------------------------- formatters

	private static JdtFormatter formatter = new DefaultFormatter();

	/**
	 * Sets default formatter
	 *
	 * @param f      formatter instance
	 */
	public static void setDefaultFormatter(JdtFormatter f) {
		if (f != null) {
			formatter = f;
		}
	}

	// ---------------------------------------------------------------- formatters usage (String conversions)

	/**
	 * Get current date/time in specified format.
	 *
	 * @param template format template
	 *
	 * @return current date/time string
	 */
	public String get(String template) {
		return formatter.get(this, template);
	}

	/**
	 * Get current date/time in default format.
	 *
	 * @return current date/time string
	 */
	public String get() {
		return get(getFormatTemplate());
	}


	/**
	 * Sets date/time from a string and specified template.
	 *
	 * @param s        string containing date time information
	 * @param template format template
	 */
	public void set(String s, String template) {
		DateTimeStamp dts = formatter.set(s, template);
		if (dts != null) {
			setDateTimeStamp(dts);
		}
	}

	/**
	 * Sets date/time from a string and default template.
	 *
	 * @param s        string containing date time information
	 */
	public void set(String s) {
		set(s, getFormatTemplate());
	}


	// ---------------------------------------------------------------- week definitions

	private static int defaultFirstDayOfWeek = 1;

	private int firstDayOfWeek = 0;

	private static int defaultMustHaveDayOfFirstWeek = 4;

	private int mustHaveDayOfFirstWeek = 0;

	/**
	 * Defines default week. Not valid values are ignored and may be used for
	 * individual settings of each of 2 input parameters.
	 *
	 * @param start  first day in week
	 * @param must   must have day of the 1st week
	 */
	public static void setDefaultWeekDefinition(int start, int must) {
		if ((start >= 1) && (start <= 7)) {
			defaultFirstDayOfWeek = start;
		}
		if ((must >= 1) && (must <= 7)) {
			defaultMustHaveDayOfFirstWeek = must;
			defaultMinimalDaysInFirstWeek = convertMin2Must(defaultFirstDayOfWeek, must);
		}
	}

	/**
	 * Defines week.
	 *
	 * @param start  first day in week
	 * @param must   must have day of the 1st week
	 */
	public void setWeekDefinition(int start, int must) {
		if ((start >= 1) && (start <= 7)) {
			firstDayOfWeek = start;
		}
		if ((must >= 1) && (must <= 7)) {
			mustHaveDayOfFirstWeek = must;
			minimalDaysInFirstWeek = convertMin2Must(getFirstDayOfWeek(), must);
		}
	}

	/**
	 * Resets default week definition.
	 */
	public static void resetDefaultWeekDefinition() {
		defaultFirstDayOfWeek = 1;
		defaultMustHaveDayOfFirstWeek = 4;
	}

	/**
	 * Resets week definition.
	 */
	public void resetWeekDefintion() {
		firstDayOfWeek = 0;
		mustHaveDayOfFirstWeek = 0;
	}


	/**
	 * Returns the first day of the week.
	 *
	 * @return first day of week
	 */
	public int getFirstDayOfWeek() {
		if (firstDayOfWeek != 0) {
			return firstDayOfWeek;
		}
		return defaultFirstDayOfWeek;
	}

	/**
	 * Returns must have day of the 1st week.
	 *
	 * @return must have day of the first week
	 */
	public int getMustHaveDayOfFirstWeek() {
		if (mustHaveDayOfFirstWeek != 0) {
			return mustHaveDayOfFirstWeek;
		}
		return defaultMustHaveDayOfFirstWeek;
	}


	// ---------------------------------------------------------------- week definitions (alt)

	private static int defaultMinimalDaysInFirstWeek = 4;

	private int minimalDaysInFirstWeek = 0;

	/**
	 * Returns minimal number of days of the first week. It is calculated from
	 * must have day of the first week.
	 *
	 * @return minimal number of days of the first week
	 */
	public int getMinimalDaysInFirstWeek() {
		if (minimalDaysInFirstWeek != 0) {
			return minimalDaysInFirstWeek;
		}
		return defaultMinimalDaysInFirstWeek;
	}


	/**
	 * Defines default week alternatively.
	 *
	 * @param start  first day in week
	 * @param min    minimal days of week
	 */
	public static void setDefaultWeekDefinitionAlt(int start, int min) {
		if ((start >= 1) && (start <= 7)) {
			defaultFirstDayOfWeek = start;
		}
		if ((min >= 1) && (min <= 7)) {
			defaultMustHaveDayOfFirstWeek = convertMin2Must(defaultFirstDayOfWeek, min);
			defaultMinimalDaysInFirstWeek = min;
		}
	}

	/**
	 * Defines week alternatively.
	 *
	 * @param start  first day in week
	 * @param min    minimal days of week
	 */

	public void setWeekDefinitionAlt(int start, int min) {
		if ((start >= 1) && (start <= 7)) {
			firstDayOfWeek = start;
		}
		if ((min >= 1) && (min <= 7)) {
			mustHaveDayOfFirstWeek = convertMin2Must(getFirstDayOfWeek(), min);
			minimalDaysInFirstWeek = min;
		}
	}

	/**
	 * Converts minimal day of week to must have day of week.
	 * Method is symmetrical.
	 *
	 * @param start  first day of week
	 * @param min    minimal day of week
	 *
	 * @return must have day of week
	 */
	private static int convertMin2Must(int start, int min) {
		int must = 8 - min + (start - 1);
		if (must > 7) {
			must -= 7;
		}
		return must;
	}

	// ---------------------------------------------------------------- isValid


	/**
	 * Checks if some string represents a valid date. It uses
	 * <code>JDateTime</code>'s default format template.
	 *
	 * @param s      string
	 *
	 * @return true if date is valid, otherwise false
	 */
	public static boolean isValid(String s) {
		return isValid(s, defaultFormatTemplate);
	}

	/**
	 * Checks if some string represents a valid date.
	 *
	 * @param s        string
	 * @param template template
	 *
	 * @return true if date is valid, otherwise false
	 */
	public static boolean isValid(String s, String template) {
		DateTimeStamp dtsOriginal = formatter.set(s, template);
		if (dtsOriginal == null) {
			return false;
		}
		return TimeUtil.isValidDateTime(dtsOriginal);
		//JDateTime jdt = new JDateTime(dtsOriginal);
		//DateTimeStamp dtsMatch = jdt.getDateTimeStamp();
		//return (dtsOriginal.compareTo(dtsMatch) == 0);
	}

	// ---------------------------------------------------------------- toString

	/**
	 * Returns date time string in YYYY-MM-DD hh:mm:s.m format.
	 *
	 * @return date time string in default format
	 * @see #get
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer(25);
		sb.append(time.year).append('-');
		if (time.month < 10) sb.append('0');
		sb.append(time.month).append('-');
		if (time.day < 10) sb.append('0');
		sb.append(time.day).append(' ');
		if (time.hour < 10) sb.append('0');
		sb.append(time.hour).append(':');
		if (time.minute < 10) sb.append('0');
		sb.append(time.minute).append(':');
		if (time.second < 10) sb.append('0');
		sb.append((int)time.second).append('.');
		int milis = getMillisecond();
		if (milis < 10) sb.append('0');
		if (milis < 100) sb.append('0');
		sb.append(milis);
		return sb.toString();
	}

}
